<FrameworkSwitchCourse {fw} />

# Por dentro da função pipeline

{#if fw === 'pt'}

<CourseFloatingBanner chapter={2}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/pt/chapter2/section2_pt.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/pt/chapter2/section2_pt.ipynb"},
]} />

{:else}

<CourseFloatingBanner chapter={2}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/pt/chapter2/section2_tf.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/pt/chapter2/section2_tf.ipynb"},
]} />

{/if}

> [!TIP]
> Esta é a primeira seção onde o conteúdo é ligeiramente diferente, dependendo se você usa PyTorch e TensorFlow. Para selecionar a plataforma que você prefere, basta alterar no botão no topo.

{#if fw === 'pt'}
<Youtube id="1pedAIvTWXk"/>
{:else}
<Youtube id="wVN12smEvqg"/>
{/if}

Vamos começar com um exemplo completo, dando uma olhada no que acontece dentro da função quando executamos o seguinte código no [Capítulo 1](/course/pt/chapter1):

```python
from transformers import pipeline

classifier = pipeline("sentiment-analysis")
classifier(
    [
        "I've been waiting for a HuggingFace course my whole life.",
        "I hate this so much!",
    ]
)
```

tendo o resultado:

```python out
[{'label': 'POSITIVE', 'score': 0.9598047137260437},
 {'label': 'NEGATIVE', 'score': 0.9994558095932007}]
```

Como visto no [Capítulo 1](/course/pt/chapter1), este pipeline agrupa os três passos: o pré-processamento, passagem das entradas através do modelo, e o pós-processamento:

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter2/full_nlp_pipeline.svg" alt="O pipeline NLP completa: tokenização do texto, conversão para IDs, e inferência através do Transformer e pela 'cabeça' do modelo."/>
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter2/full_nlp_pipeline-dark.svg" alt="O pipeline NLP completa: tokenização do texto, conversão para IDs, e inferência através do Transformer e pela 'cabeça' do modelo."/>
</div>

Vamos rever rapidamente cada um deles.

## Pré-processamento com o tokenizer

Como outras redes neurais, os Transformers não podem processar texto bruto diretamente, portanto, o primeiro passo do nosso pipeline é converter as entradas de texto em números que o modelo possa fazer sentido. Para fazer isso, usamos um *tokenizer*, que será responsável por:

- Dividir a entrada em palavras, sub-palavras ou símbolos (como pontuação) que são chamados de *tokens*.
- Mapeando cada ficha para um número inteiro
- Adicionando entradas adicionais que podem ser úteis ao modelo

Todo esse pré-processamento precisa ser feito exatamente da mesma forma que quando o modelo foi pré-treinado, então precisamos primeiro baixar essas informações do [Model Hub](https://huggingface.co/models). Para isso, utilizamos a classe `AutoTokenizer` e seu método `from_pretrained()`. Utilizando o nome do ponto de verificação de nosso modelo, ele irá buscar automaticamente os dados associados ao tokenizer do modelo e armazená-los em cache (portanto, ele só é baixado na primeira vez que você executar o código abaixo).

Desde que o checkpoint default do pipeline `sentiment-analysis` é `distilbert-base-uncased-finetuned-sst-2-english` (você pode ver o card do modelo [aqui](https://huggingface.co/distilbert-base-uncased-finetuned-sst-2-english)), então executamos o seguinte:

```python
from transformers import AutoTokenizer

checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
```

Assim que tivermos o tokenizer, podemos passar diretamente nossas frases para ele e receberemos de volta um dicionário que está pronto para alimentar nosso modelo! A única coisa que falta fazer é converter a lista de identificações de entrada em tensores.

Você pode usar 🤗 Transformers sem ter que se preocupar com qual estrutura ML é usada como backend; pode ser PyTorch ou TensorFlow, ou Flax para alguns modelos. Entretanto, os Transformers só aceitam *tensores* como entrada. Se esta é a primeira vez que você ouve falar de tensores, você pode pensar neles como matrizes da NumPy. Uma matriz NumPy pode ser um escalar (0D), um vetor (1D), uma matriz (2D), ou ter mais dimensões. É efetivamente um tensor; os tensores de outras estruturas ML comportam-se de forma semelhante, e geralmente são tão simples de instanciar como os arrays da NumPy.

Para especificar o tipo de tensores que queremos recuperar (PyTorch, TensorFlow ou NumPy), utilizamos o argumento `return_tensors`:

{#if fw === 'pt'}
```python
raw_inputs = [
    "I've been waiting for a HuggingFace course my whole life.",
    "I hate this so much!",
]
inputs = tokenizer(raw_inputs, padding=True, truncation=True, return_tensors="pt")
print(inputs)
```
{:else}
```python
raw_inputs = [
    "I've been waiting for a HuggingFace course my whole life.",
    "I hate this so much!",
]
inputs = tokenizer(raw_inputs, padding=True, truncation=True, return_tensors="tf")
print(inputs)
```
{/if}

Não se preocupe ainda com o truncamento e o padding; explicaremos isso mais tarde. As principais coisas a lembrar aqui são que você pode passar uma frase ou uma lista de frases, bem como especificar o tipo de tensores que você quer recuperar (se nenhum tipo for passado, você receberá uma lista de listas como resultado).

{#if fw === 'pt'}

Eis como são os resultados como tensores PyTorch:

```python out
{
    'input_ids': tensor([
        [  101,  1045,  1005,  2310,  2042,  3403,  2005,  1037, 17662, 12172, 2607,  2026,  2878,  2166,  1012,   102],
        [  101,  1045,  5223,  2023,  2061,  2172,   999,   102,     0,     0,     0,     0,     0,     0,     0,     0]
    ]), 
    'attention_mask': tensor([
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0]
    ])
}
```
{:else}

Eis como são os resultados como tensores TensorFlow:

```python out
{
    'input_ids': <tf.Tensor: shape=(2, 16), dtype=int32, numpy=
        array([
            [  101,  1045,  1005,  2310,  2042,  3403,  2005,  1037, 17662, 12172,  2607,  2026,  2878,  2166,  1012,   102],
            [  101,  1045,  5223,  2023,  2061,  2172,   999,   102,     0,     0,     0,     0,     0,     0,     0,     0]
        ], dtype=int32)>, 
    'attention_mask': <tf.Tensor: shape=(2, 16), dtype=int32, numpy=
        array([
            [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
            [1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0]
        ], dtype=int32)>
}
```
{/if}

A saída em si é um dicionário contendo duas chaves, `input_ids' e `attention_mask'. O `input_ids' contém duas linhas de inteiros (uma para cada frase) que são os identificadores únicos dos tokens em cada frase. Explicaremos o que é a "máscara de atenção" (attention mask) mais adiante neste capítulo. 

## Indo adianta pelo modelo

{#if fw === 'pt'}
Podemos baixar nosso modelo pré-treinado da mesma forma que fizemos com nosso tokenizer. 🤗 Transformers fornece uma classe `AutoModel` que também tem um método `from_pretrained()`:

```python
from transformers import AutoModel

checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
model = AutoModel.from_pretrained(checkpoint)
```
{:else}
Podemos baixar nosso modelo pré-treinado da mesma forma que fizemos com nosso tokenizer. 🤗 Transformers fornece uma classe "TFAutoModel" que também tem um método "from_pretrained":


```python
from transformers import TFAutoModel

checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
model = TFAutoModel.from_pretrained(checkpoint)
```
{/if}

Neste trecho de código, fizemos o download do mesmo checkpoint que usamos anteriormente em nosso pipeline (já deveria estar em cache) e instanciamos um modelo com ele.

Esta arquitetura contém apenas o módulo base do Transformer: dadas algumas entradas, ele produz o que chamaremos de *hidden states* (estados ocultos), também conhecidos como *features* (características). Para cada modelo de entrada, recuperaremos um vetor de alta dimensionalidade representando a **compreensão contextual dessa entrada pelo Transformer***.

Se isto não faz sentido, não se preocupe com isso. Explicaremos tudo isso mais tarde.

Embora estes hidden states possam ser úteis por si mesmos, eles geralmente são entradas para outra parte do modelo, conhecida como *head* (cabeça). No [Capítulo 1](/course/pt/chapter1), as diferentes tarefas poderiam ter sido realizadas com a mesma arquitetura, mas cada uma destas tarefas teria uma head diferente associada a ela.

### Um vetor de alta dimensionalidade?

A saída vetorial pelo módulo do Transformer é geralmente grande. Geralmente tem três dimensões:

- **Tamanho do lote** (Batch size): O número de sequências processadas de cada vez (2 em nosso exemplo).
- **Tamanho da sequencia** (Sequence length): O comprimento da representação numérica da sequência (16 em nosso exemplo).
- **Tamanho oculto** (Hidden size): A dimensão vetorial de cada modelo de entrada.

Diz-se que é "de alta dimensionalidade" por causa do último valor. O tamanho oculto pode ser muito grande (768 é comum para modelos menores, e em modelos maiores isto pode chegar a 3072 ou mais).

Podemos ver isso se alimentarmos os inputs que pré-processamos para nosso modelo:

{#if fw === 'pt'}
```python
outputs = model(**inputs)
print(outputs.last_hidden_state.shape)
```

```python out
torch.Size([2, 16, 768])
```
{:else}
```py
outputs = model(inputs)
print(outputs.last_hidden_state.shape)
```

```python out
(2, 16, 768)
```
{/if}

Observe que as saídas dos 🤗 Transformer se comportam como 'tuplas nomeadas' (namedtuple) ou dicionários. Você pode acessar os elementos por atributos (como fizemos) ou por chave (`outputs["last_hidden_state"]`), ou mesmo por índice se você souber exatamente onde o que está procurando (`outputs[0]`).

### Cabeça do modelo (model heads): Fazendo sentido a partir dos números

As *heads* do modelo usam o vetor de alta dimensionalidade dos hidden states como entrada e os projetam em uma dimensão diferente. Eles são geralmente compostos de uma ou algumas camadas lineares:

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter2/transformer_and_head.svg" alt="Uma rede Transformer ao lado de sua head."/>
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter2/transformer_and_head-dark.svg" alt="Uma rede Transformer ao lado de sua head."/>
</div>

A saída do Transformer é enviada diretamente para a *head* do modelo a ser processado.

Neste diagrama, o modelo é representado por sua camada de embeddings (vetores) e pelas camadas subsequentes. A camada de embeddings converte cada ID de entrada na entrada tokenizada em um vetor que representa o token associado. As camadas subsequentes manipulam esses vetores usando o mecanismo de atenção para produzir a representação final das sentenças.

Há muitas arquiteturas diferentes disponíveis no 🤗 Transformers, com cada uma projetada em torno de uma tarefa específica. Aqui está uma lista por **algumas** destas tarefas:

- `*Model` (recuperar os hidden states)
- `*ForCausalLM`
- `*ForMaskedLM`
- `*ForMultipleChoice`
- `*ForQuestionAnswering`
- `*ForSequenceClassification`
- `*ForTokenClassification`
- e outros 🤗

{#if fw === 'pt'}
Para nosso exemplo, precisaremos de um modelo com uma *head* de classificação em sequencia (para poder classificar as sentenças como positivas ou negativas). Portanto, não utilizaremos a classe `AutoModel`, mas sim, a classe `AutoModelForSequenceClassification`:

```python
from transformers import AutoModelForSequenceClassification

checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
model = AutoModelForSequenceClassification.from_pretrained(checkpoint)
outputs = model(**inputs)
```
{:else}
Para nosso exemplo, precisaremos de um modelo com uma *head* de classificação em sequencia (para poder classificar as sentenças como positivas ou negativas). Portanto, não utilizaremos a classe `TFAutoModel`, mas sim, a classe `TFAutoModelForSequenceClassification`:

```python
from transformers import TFAutoModelForSequenceClassification

checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint)
outputs = model(inputs)
```
{/if}

Agora se observarmos o tamanho dos nossos inputs, a dimensionalidade será muito menor: a *head* do modelo toma como entrada os vetores de alta dimensionalidade que vimos anteriormente, e os vetores de saída contendo dois valores (um por label):

```python
print(outputs.logits.shape)
```

{#if fw === 'pt'}
```python out
torch.Size([2, 2])
```
{:else}
```python out
(2, 2)
```
{/if}

Como temos apenas duas sentenças e duas labels, o resultado que obtemos de nosso modelo é de tamanho 2 x 2.

## Pós-processamento da saída

Os valores que obtemos como resultado de nosso modelo não fazem necessariamente sentido sozinhos. Vamos dar uma olhada:

```python
print(outputs.logits)
```

{#if fw === 'pt'}
```python out
tensor([[-1.5607,  1.6123],
        [ 4.1692, -3.3464]], grad_fn=<AddmmBackward>)
```
{:else}
```python out
<tf.Tensor: shape=(2, 2), dtype=float32, numpy=
    array([[-1.5606991,  1.6122842],
           [ 4.169231 , -3.3464472]], dtype=float32)>
```
{/if}

Nosso modelo previu `[-1.5607, 1.6123]` para a primeira frase e `[ 4.1692, -3.3464]` para a segunda. Essas não são probabilidades, mas *logits*, a pontuação bruta e não normalizada produzida pela última camada do modelo. Para serem convertidos em probabilidades, eles precisam passar por uma camada [SoftMax](https://en.wikipedia.org/wiki/Softmax_function) (todas saídas dos 🤗 Transformers produzem  *logits*, já que a função de *loss* (perda) para treinamento geralmente fundirá a última função de ativação, como SoftMax, com a função de *loss* real, por exemplo a *cross entropy*):

{#if fw === 'pt'}
```py
import torch

predictions = torch.nn.functional.softmax(outputs.logits, dim=-1)
print(predictions)
```
{:else}
```py
import tensorflow as tf

predictions = tf.math.softmax(outputs.logits, axis=-1)
print(predictions)
```
{/if}

{#if fw === 'pt'}
```python out
tensor([[4.0195e-02, 9.5980e-01],
        [9.9946e-01, 5.4418e-04]], grad_fn=<SoftmaxBackward>)
```
{:else}
```python out
tf.Tensor(
[[4.01951671e-02 9.59804833e-01]
 [9.9945587e-01 5.4418424e-04]], shape=(2, 2), dtype=float32)
```
{/if}

Agora podemos ver que o modelo previu `[0.0402, 0.9598]` para a primeira frase e `[0.9995, 0.0005]` para a segunda. Estas são pontuações de probabilidade reconhecíveis.

Para obter as etiquetas correspondentes a cada posição, podemos inspecionar o atributo `id2label` da configuração do modelo (mais sobre isso na próxima seção):

```python
model.config.id2label
```

```python out
{0: 'NEGATIVE', 1: 'POSITIVE'}
```

Agora podemos concluir que o modelo previu o seguinte:

- A primeira frase: NEGATIVE: 0.0402, POSITIVE: 0.9598
- Segunda frase: NEGATIVE: 0.9995, POSITIVE: 0.0005

Reproduzimos com sucesso as três etapas do pipeline: o pré-processamento, passagem das entradas através do modelo, e o pós-processamento! Agora, vamos levar algum tempo para mergulhar mais fundo em cada uma dessas etapas.

> [!TIP]
> ✏️ **Experimente!** Escolha duas (ou mais) textos próprios e passe-os através do pipeline `sentiment-analysis`. Em seguida, replique as etapas que você mesmo viu aqui e verifique se você obtém os mesmos resultados!
